package org.ws.httphelper.support.fetcher;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.ws.httphelper.exception.FetchException;
import org.ws.httphelper.model.config.ClientConfig;
import org.ws.httphelper.model.http.ContentType;
import org.ws.httphelper.model.http.RequestData;
import org.ws.httphelper.model.http.ResponseData;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;

public class WebClientFetcher extends AbstractHttpFetcher {

    private final ThreadLocal<WebClient> webClientThreadLocal = new ThreadLocal<>();
    private final AtomicReference<Queue<WebClient>> webClientQueue;

    public WebClientFetcher(ClientConfig clientConfig) {
        super(clientConfig);
        this.webClientQueue = new AtomicReference<>(Queues.newConcurrentLinkedQueue());
    }

    @Override
    public ResponseData fetch(RequestData requestData) throws FetchException {
        WebClient webClient = null;
        try {
            webClient = this.webClientQueue.get().poll();
            if(webClient == null){
                webClient = makeWebClient();
            }
            this.webClientThreadLocal.set(webClient);
            WebRequest webRequest = makeWebRequest(requestData);
            Page page = this.webClientThreadLocal.get().getPage(webRequest);
            webClient.waitForBackgroundJavaScriptStartingBefore(200);
            webClient.waitForBackgroundJavaScript(this.clientConfig.getTimeout());
            WebResponse webResponse = page.getWebResponse();
            ResponseData responseData = makeResponseData(webResponse);
            page.cleanUp();
            return responseData;
        }
        catch (Exception e){
            throw new FetchException(e.getMessage(),e);
        }
        finally {
            this.webClientThreadLocal.remove();
            if(webClient != null) {
                this.webClientQueue.get().add(webClient);
            }
        }
    }

    private WebClient makeWebClient(){
        WebClient webClient = new WebClient(BrowserVersion.CHROME);

        int timeout = this.clientConfig.getTimeout();

        webClient.getOptions().setCssEnabled(false);
        webClient.getOptions().setJavaScriptEnabled(true);
        webClient.getOptions().setRedirectEnabled(this.clientConfig.isFollowRedirects());
        webClient.getCookieManager().setCookiesEnabled(this.clientConfig.isEnableCookie());
        webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
        webClient.getOptions().setTimeout(timeout);
        webClient.getOptions().setThrowExceptionOnScriptError(false);
        webClient.getOptions().setDoNotTrackEnabled(true);
        webClient.setJavaScriptTimeout(timeout);
        // 设置Ajax异步
        webClient.setAjaxController(new NicelyResynchronizingAjaxController());

        if(this.clientConfig.isEnableSsl()){
            webClient.getOptions().setUseInsecureSSL(true);
        }

        return webClient;
    }

    private WebRequest makeWebRequest(RequestData requestData) throws MalformedURLException {
        String url = requestData.getUrl();
        HttpMethod httpMethod = HttpMethod.valueOf(requestData.getMethod().name());
        WebRequest webRequest = new WebRequest(new URL(url),httpMethod);
        Map<String, String> headers = requestData.getHeaders();
        if(MapUtils.isNotEmpty(headers)) {
            webRequest.setAdditionalHeaders(headers);
        }
        Object body = requestData.getBody();
        // The request body may only be set for POST, PUT or PATCH requests!
        if(httpMethod == HttpMethod.POST
                || httpMethod == HttpMethod.PUT
                || httpMethod == HttpMethod.PATCH) {
            if (body != null && requestData.getContentType() != ContentType.EMPTY) {
                webRequest.setRequestBody(String.valueOf(body));
            }
        }
        return webRequest;
    }

    private ResponseData makeResponseData(WebResponse webResponse) throws IOException {
        ResponseData responseData = new ResponseData();
        int statusCode = webResponse.getStatusCode();
        boolean successful = statusCode >= 200 && statusCode < 300;
        responseData.setWasteTime(webResponse.getLoadTime());
        responseData.setStatus(statusCode);
        String contentType = webResponse.getContentType();
        if(successful && webResponse.getContentLength() > 0){
            if(isStringBody(contentType)){
                responseData.setBody(webResponse.getContentAsString());
            }
            else {
                InputStream inputStream = webResponse.getContentAsStream();
                byte[] bytes = IOUtils.toByteArray(inputStream);
                responseData.setBody(bytes);
            }
        }
        List<NameValuePair> headers = webResponse.getResponseHeaders();
        Map<String, String> headersMap = Maps.newHashMap();
        if(headers != null){
            for (NameValuePair header : headers) {
                headersMap.put(header.getName(),header.getValue());
            }
        }
        responseData.setHeaders(headersMap);

        return responseData;
    }

}
